using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace ZerAx.Net
{
    public class TcpConnection : IConnection
    {
        private readonly Socket client;
        private readonly byte[] buffer;
        private readonly RingMemoryStream readStream;

        private readonly Queue<Packet> msgToSend = new();

        private bool closed = false;

        public TcpConnection() : this(new Socket(SocketType.Stream, ProtocolType.Tcp)) { }

        public TcpConnection(Socket client)
        {
            this.client = client;
            buffer = new byte[1024];
            readStream = new();
            client.Blocking = false;
        }

        public void Connect(IPEndPoint endPoint)
        {
            try
            {
                client.Connect(endPoint);
            }
            catch (SocketException e)
            {
                if (e.SocketErrorCode != SocketError.WouldBlock)
                {
                    throw e;
                }
            }
            finally
            {
                closed = false;
            }
        }

        //public bool IsConnecting => connected;
        public bool IsConnecting => !closed && client.Poll(0, SelectMode.SelectWrite);

        public void Disconnect()
        {
            closed = true;
            client.Shutdown(SocketShutdown.Both);
            client.Close();
        }

        public bool Poll(out Packet packet) => (packet = Packet.From(readStream)) != null;

        public void Send(Packet packet)
        {
            msgToSend.Enqueue(packet);
        }

        public void Update()
        {
            if (!IsConnecting) return;

            if (client.Poll(0, SelectMode.SelectWrite))
            {
                while (msgToSend.Count > 0)
                {
                    client.Send(msgToSend.Dequeue().GetBytes());
                }
            }

            if (client.Poll(0, SelectMode.SelectRead))
            {
                try
                {
                    int len = client.Receive(buffer);
                    if (len <= 0)
                    {
                        Disconnect();
                        return;
                    }

                    readStream.Seek(0, SeekOrigin.End);
                    readStream.Flush();
                    readStream.Write(buffer, 0, len);
                }
                catch
                {
                    Disconnect();
                    return;
                }
            }
        }
    }
}